#include "wm_fwup.h"

extern HAL_StatusTypeDef FlashFastWriteInit(void);
extern HAL_StatusTypeDef FlashFastWrite(uint32_t addr, uint8_t *buf, uint32_t len);
extern HAL_StatusTypeDef FlashFastWriteDestroy(uint32_t addr);

static HAL_StatusTypeDef Fwup_Img_Header_Check(IMAGE_HEADER_PARAM_ST *header)
{
	CRC_HandleTypeDef crcContext;
	uint32_t value = 0;
	uint32_t len = 0;
	
	if ((header->magic_no != SIGNATURE_WORD) ||
		(header->img_addr % IMAGE_START_ADDR_MSK))
	{
		return HAL_ERROR;
	}
	
	len = sizeof(IMAGE_HEADER_PARAM_ST) - 4;
	HAL_CRYPTO_CRC_Init(&crcContext, 0xFFFFFFFF, CRYPTO_CRC_TYPE_32, (OUTPUT_REFLECT | INPUT_REFLECT));
	HAL_CRYPTO_CRC_Update(&crcContext, (uint8_t *)header, len);
	HAL_CRYPTO_CRC_Final(&crcContext, &value);
	if (header->hd_checksum == value)
	{
		return HAL_OK;
	}
	else
	{
		return HAL_ERROR;
	}
}

void HAL_FWUP_Init(FWUP_HandleTypeDef *hfwup)
{
	hfwup->total_len = 0;
	hfwup->received_len = 0;
	hfwup->program_base = 0;
	hfwup->program_offset = 0;
	hfwup->status = FWUP_STATUS_IDLE;
	FlashFastWriteInit();
}

HAL_StatusTypeDef HAL_FWUP_Write(FWUP_HandleTypeDef *hfwup, uint8_t *pData, uint32_t Size)
{
	uint32_t len, err;
	IMAGE_HEADER_PARAM_ST *header = &(hfwup->header);
	
	if ((hfwup->status == FWUP_STATUS_ERROR) || (hfwup->status == FWUP_STATUS_COMPLETE))
	{
		goto OUT;
	}
	if (hfwup->received_len < sizeof(IMAGE_HEADER_PARAM_ST))
	{
		len = sizeof(IMAGE_HEADER_PARAM_ST) - hfwup->received_len;
		len = (Size > len) ? len : Size;
		memcpy((void *)header, (void *)pData, len);
		hfwup->received_len += len;
		pData += len;
		Size -= len;
		if (hfwup->received_len == sizeof(IMAGE_HEADER_PARAM_ST))
		{
			if (Fwup_Img_Header_Check(header) != HAL_OK)
			{
				goto OUT;
			}
			hfwup->status = FWUP_STATUS_BUSY;
			hfwup->program_base = header->upgrade_img_addr;
			hfwup->program_offset = 0;
			hfwup->total_len = header->img_len + sizeof(IMAGE_HEADER_PARAM_ST);
			err = FlashFastWrite(hfwup->program_base + hfwup->program_offset, (uint8_t *)header, sizeof(IMAGE_HEADER_PARAM_ST));
			if (err != HAL_OK)
			{
				goto OUT;
			}
			hfwup->program_offset += sizeof(IMAGE_HEADER_PARAM_ST);
		}
	}
	if (Size > 0)
	{
		err = FlashFastWrite(hfwup->program_base + hfwup->program_offset, pData, Size);
		if (err != HAL_OK)
		{
			goto OUT;
		}
		hfwup->received_len += Size;
		hfwup->program_offset += Size;
		if (hfwup->received_len >= hfwup->total_len)
		{
			uint32_t offset, left;
			CRC_HandleTypeDef crcContext;
			uint32_t checksum;
			uint8_t *p;
			
			err = FlashFastWriteDestroy(hfwup->program_base + hfwup->program_offset);
			if (err != HAL_OK)
			{
				goto OUT;
			}
			p = malloc(1024);
			if (p == NULL)
			{
				goto OUT;
			}
			offset = sizeof(IMAGE_HEADER_PARAM_ST);
			if (header->img_attr.b.signature)
			{
				left = hfwup->total_len - 128 - offset;
			}
			else
			{
				left = hfwup->total_len- offset;
			}
			HAL_CRYPTO_CRC_Init(&crcContext, 0xFFFFFFFF, CRYPTO_CRC_TYPE_32, (OUTPUT_REFLECT | INPUT_REFLECT));
			while (left > 0)
			{
				len = (left > 1024) ? 1024 : left;
				err = HAL_FLASH_Read((hfwup->program_base + offset), p, len);
				if (err != HAL_OK)
				{
					free(p);
					goto OUT;
				}
				HAL_CRYPTO_CRC_Update(&crcContext, p, len);
				offset += len;
				left -= len;
			}
			HAL_CRYPTO_CRC_Final(&crcContext, &checksum);
			free(p);
			if (header->org_checksum != checksum)
			{
				goto OUT;
			}
			else
			{
				err = HAL_FLASH_Write(FLASH_OTA_FLAG_ADDR, (uint8_t *)&checksum, sizeof(checksum));
				if (err != HAL_OK)
				{
					goto OUT;
				}
				hfwup->status = FWUP_STATUS_COMPLETE;
			}
		}
	}
	return HAL_OK;
OUT:
	hfwup->status = FWUP_STATUS_ERROR;
	return HAL_ERROR;
}

FWUP_StatusDef HAL_FWUP_Get_Status(FWUP_HandleTypeDef *hfwup)
{
	return hfwup->status;
}